{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "h3z5i2X45vTk"
   },
   "source": [
    "### **Swarm-Style Agent Tutorial with LanceDB for Retrieval**  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BHGgH_UH5vTl"
   },
   "source": [
    "In this tutorial, we are using an **MSwarm-style agent system**, where multiple agents communicate with each other to collaboratively generate answers. **LanceDB** is used for efficient information retrieval, including full-text search (FTS) and other advanced query capabilities.  \n",
    "\n",
    "\n",
    "\n",
    "![Trip Planner Agent](https://github.com/akashAD98/vectordb-recipes/blob/new_planner_swarm_lancedb/assets/trip_planner_agent.png?raw=1)\n",
    "\n",
    "\n",
    "### **Key Features**  \n",
    "🚀 **Multi-Agent Collaboration** – Specialized agents work together, seamlessly passing context to one another.  \n",
    "🔧 **Customizable Handoff Tools** – Built-in mechanisms for smooth communication between agents.  \n",
    "📂 **LanceDB for Data Retrieval** – High-performance vector search and full-text search for accurate and fast information retrieval.  \n",
    "🌍 **Travel Agent Use Case** – Agents collaborate to handle different aspects of travel planning, ensuring efficient and context-aware responses.  \n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "a0k_ssua5vTn"
   },
   "source": [
    "### Importing the relevant libraires"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "A9QFbWf05yUT"
   },
   "outputs": [],
   "source": [
    "%%capture\n",
    "!pip install lancedb pandas sentence-transformers requests openai tantivy langchain_openai langgraph langgraph_swarm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "qDN-RFqxr5ya",
    "outputId": "f6f7b1e7-486d-4d9e-94e8-6ed63b23f1fd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Files downloaded and loaded successfully!\n"
     ]
    }
   ],
   "source": [
    "import datetime\n",
    "from typing import Callable, List, Dict, Any\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.checkpoint.memory import InMemorySaver\n",
    "from langgraph.prebuilt import create_react_agent\n",
    "from langgraph_swarm import create_handoff_tool, create_swarm\n",
    "\n",
    "import lancedb\n",
    "from lancedb.embeddings import EmbeddingFunctionRegistry\n",
    "from lancedb.pydantic import LanceModel, Vector\n",
    "import pandas as pd\n",
    "from openai import OpenAI\n",
    "\n",
    "import os\n",
    "import json\n",
    "import ast\n",
    "import pandas as pd\n",
    "\n",
    "# Set OpenAI API key\n",
    "OPENAI_API_KEY = \"sk-proj-your_key\"\n",
    "\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = OPENAI_API_KEY\n",
    "client = OpenAI(api_key=OPENAI_API_KEY)\n",
    "\n",
    "# Initialize LanceDB and load data\n",
    "db = lancedb.connect(\"/tmp/plannerh&f\")\n",
    "\n",
    "# Define directory and file paths\n",
    "save_dir = \"/content/data\"\n",
    "flight_url = \"https://raw.githubusercontent.com/akashAD98/dummy_data/main/CSV_Data/final_flight_data.csv\"\n",
    "hotel_url = \"https://raw.githubusercontent.com/akashAD98/dummy_data/main/CSV_Data/final_hotels_dataN.csv\"\n",
    "\n",
    "flight_path = os.path.join(save_dir, \"final_flight_data.csv\")\n",
    "hotel_path = os.path.join(save_dir, \"final_hotels_dataN.csv\")\n",
    "\n",
    "# Create directory if not exists\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "\n",
    "# Download files if not already present\n",
    "if not os.path.exists(flight_path):\n",
    "    os.system(f\"wget -O {flight_path} {flight_url}\")\n",
    "\n",
    "if not os.path.exists(hotel_path):\n",
    "    os.system(f\"wget -O {hotel_path} {hotel_url}\")\n",
    "\n",
    "# Load data\n",
    "flight_data = pd.read_csv(flight_path, index_col=0)\n",
    "df_hotel = pd.read_csv(hotel_path, index_col=0)\n",
    "\n",
    "# Print confirmation\n",
    "print(\"Files downloaded and loaded successfully!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 496,
     "referenced_widgets": [
      "ddbd47f98e7449fb89a57790739109f7",
      "860b2ca1464a475ba354d70c888377e9",
      "2ba63d45688d4328860ce23995fb11f9",
      "b1445277b027427fbb4a42b60c2ac3ab",
      "e031693c0193466b82a86bd9c20b8a43",
      "df080ebbbb194dde8b5a720b173390fd",
      "597d61702e5441168bc7121213d9926b",
      "a3fa6e56bc5f4cb18d7a72700ff81eb6",
      "5cdd4687561a44a68a400eb23b6f0e73",
      "a9ccee60d1c54a74acc4f1e6765a2f46",
      "ea29fcdb02514b80b7b9c29640dc1388",
      "457206a3fd5c4adda545eb2755e0fe0b",
      "2cfacfb21e244f6a8b295cdc8ccb71a4",
      "9720b8a5cd094d56bbb228ed03d4b5c5",
      "82bf2b70cced42cebb3801cc41f284ec",
      "14a0130b90e04f62846dc4124ca9f57f",
      "1e6128d1280c404382acf3fbb01e16dc",
      "b1f31de1583a45be84455e7f18fb84f3",
      "12c6973c4fa943cca6341982ff5a0b88",
      "3c09eaf509bc4c958165763580027d72",
      "05f479eb43bb48c79a4c2e1a8ef88eff",
      "7bfde43ad2bb4b2486bbb7f388cb956a",
      "b6d41b8134c14433a503348603059b4a",
      "e7a02dc4c3424df4a6ab577c94e456a6",
      "d759b749a1284776a913aad8c9ebdbfb",
      "274f4342e27343f185bbab54f73b024a",
      "cdfecc97adb3486da2ceef354b2dd2fc",
      "c70a9b3eeefb4dc184c7e310fb4a3dc9",
      "d77fbdd9ff12446d9f339485a16d92b7",
      "6d34910b93b64bed9ad0a1003824da47",
      "0285f1648cd64242bd19c42c55f7ee8e",
      "745d5b90f9184f45b7d47a325e9109e4",
      "0d1597f194b7405a9934f2fa4ef4e764",
      "1f15dfc6b1fa4e2fa78d67508f8a5825",
      "5f4078c2d8bc4c818d86543aed7b2e89",
      "223b5ba00d1140caadc0dfc418b8b1ac",
      "6044cd8a408a47d887115d10b74ca24a",
      "f9170417df1a4dd585c0863b359811a9",
      "1ef0567f86db493d983917b6aa58d28e",
      "59442cc6be884d74a7a83c0da250042b",
      "e819ce7c44174c06b1c27aa0c82eebff",
      "0251424f12a44ce09cd6bee90a66de7c",
      "bb2af0ff97934255a0f7f63a9b4d8da5",
      "1e994eaf799a45caa1d750ad50c53616",
      "82ca3b21107c4098b38b409f7859894f",
      "7dbec3cfb5e44d2fb9aad54bd007e412",
      "c39c03f3942f4cfabafaa28f28f9d340",
      "e504a60d8bf94a19beeec2fde5540434",
      "9e8cae34eb9d44d186066d68797affbf",
      "b75c4da256544c959f35bd5dc4c602f0",
      "fad749c94c034ca993e9f08ba213c54f",
      "9e70eebf7ca1454b994bb2e76c4b7263",
      "3a5139af305a4cb881b3410af5c73113",
      "4cdecaf30094495f8e182d0ba7ac6e30",
      "9272d66bc3ff42fbb4a589830c63bb0f",
      "1bb9a61ef9714e82801c5e4da9f3beba",
      "0ae696d69d7242c09fc5397e5c6fe2b0",
      "646dd721e2b545099e518ec2ba72ee81",
      "621d20408794480fa5b8a9e9ba9e2fea",
      "0786396d47c1452bb9a5b73f7d227601",
      "404a33a08bd342c288c9926b1f8a85ef",
      "b8a21880881d47fe82b9522c1b055d00",
      "07908912a88c443ba1d5fdbebe9cb29e",
      "1f85e3e3bced447eaac6b41537685c64",
      "1d6f9f47448a41bf8d4e0d63e3abc233",
      "3abbb2e65dcd475fb224e62c79dca1e3",
      "1607009aaccf447a930f9dcfed7d4104",
      "42255352df844524854e12ba2a3725fe",
      "d5c28d8221c94c0384ba51de711d335e",
      "f889d6d94882406281da2a7114020a66",
      "35d42735b36c41a9806ffcd12be96fd2",
      "2cf8df1844954340b107095e7619bae7",
      "4eb51184e877418b893516ae424f1ae7",
      "ccb77e7b86124237be708bc16e508d9f",
      "e134c68aba7a4b83ac0c3d0a67a6ddc3",
      "226cf2d5c95c46fb931f04ca565eb004",
      "62dace4a343946c7a7d4b09fe310186c",
      "08fa819391584891a2ca501d728e6ab4",
      "432e294b0f5648fe981d6e115867f679",
      "bca0490635d3416f9e9560fed217ec2d",
      "8d465f4e9f684b84bc7647a44a09cdb6",
      "95fcc42d71c44cf390071412d619806b",
      "54e68f19a52945a3972ca6745a764994",
      "dd1a46f21f9741fab5a846621857ade6",
      "20cc89698f4d4d378cb731059707ad2a",
      "f4ce54c274ca43028420a53eb483279f",
      "340b24625aab4334a62c183594570f69",
      "62a3c30584f54bbd90f9d65522603150",
      "d6d7b0d377b54f818581ef1115ef1424",
      "376d97f1740642f2bf8cd827718e4c1e",
      "24d0bc0236f34c81b00dee035d2e911a",
      "0a88374a8f124ee2874db13787ba6675",
      "97db7cad144c431da73c0c79fc317dfc",
      "bde6437c6b4548a69147dadf65a6c02f",
      "33dbc99273984413a7d2a564ef462586",
      "2642bf9204924d4d89bee843f03c0377",
      "52bc4d496bb941939be4b92cbfc90104",
      "1ed3568c28ff42acb559c59010228ddf",
      "018411558f3d4db48f869b27f87380fc",
      "e4205cb857d44dc1866d875ad0a9e0f9",
      "e472ec84c5a54288a34af8b887518aae",
      "19b5949b2b204605826a93b440c2d0bf",
      "641ca689b2684a30b0368348f429c95b",
      "df0ed509de914b128d22695de41fb823",
      "78fa95a144994201879669d8b6f75755",
      "68487c49d0734654b60afebadaa86144",
      "8484c5d7f2464a5e84ad6b23745e1bed",
      "3f960af6ad054561a339dc985c638b18",
      "ff57b5200075425f88587a7dfda212bd",
      "f46e7585a47e49e6997a2aa0de50bd43",
      "859f768cf5214049a91d10fbd2c73ff8",
      "893cea3e31f34e5a9a8c297a665850b4",
      "bd74c6b74fc24981895bcca76051a976",
      "a8ba43d57ec2403e97e3b3920110c268",
      "3809098fe3df444aa54b4c72be029c1f",
      "8b65bbea3717406884e1d25cdaf2c4e9",
      "4f72e9d788d4463f87d559b0f43edda9",
      "e3e0c5d9e5eb49ae990f94c2a066c3ea",
      "5b33cbb87ae441e2afecb8b8a27c4fbf",
      "ef8fc47624be4cc6933cafad8480dd9e",
      "d4b44000f8da4519bcc8bf41acb68d63"
     ]
    },
    "id": "sBTg5Ri5-jFg",
    "outputId": "49a2cc8d-1a4e-41d8-cde9-6b63a390f4a8"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.11/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n",
      "The secret `HF_TOKEN` does not exist in your Colab secrets.\n",
      "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n",
      "You will be able to reuse this secret in all of your notebooks.\n",
      "Please note that authentication is recommended but still optional to access public models or datasets.\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ddbd47f98e7449fb89a57790739109f7",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "modules.json:   0%|          | 0.00/349 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "457206a3fd5c4adda545eb2755e0fe0b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "config_sentence_transformers.json:   0%|          | 0.00/116 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b6d41b8134c14433a503348603059b4a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "README.md:   0%|          | 0.00/10.5k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1f15dfc6b1fa4e2fa78d67508f8a5825",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "sentence_bert_config.json:   0%|          | 0.00/53.0 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "82ca3b21107c4098b38b409f7859894f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "config.json:   0%|          | 0.00/612 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1bb9a61ef9714e82801c5e4da9f3beba",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "model.safetensors:   0%|          | 0.00/90.9M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1607009aaccf447a930f9dcfed7d4104",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "tokenizer_config.json:   0%|          | 0.00/350 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "08fa819391584891a2ca501d728e6ab4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "vocab.txt:   0%|          | 0.00/232k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "d6d7b0d377b54f818581ef1115ef1424",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "tokenizer.json:   0%|          | 0.00/466k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e4205cb857d44dc1866d875ad0a9e0f9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "special_tokens_map.json:   0%|          | 0.00/112 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "859f768cf5214049a91d10fbd2c73ff8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "config.json:   0%|          | 0.00/190 [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Set up embeddings\n",
    "registry = EmbeddingFunctionRegistry.get_instance()\n",
    "func = registry.get(\"sentence-transformers\").create(device=\"cpu\")\n",
    "\n",
    "\n",
    "# Define schemas\n",
    "class FlightWords(LanceModel):\n",
    "    id: str = func.SourceField()  # Index column\n",
    "    Flight_Number: str = func.SourceField()\n",
    "    Airline: str = func.SourceField()\n",
    "    Origin_City: str = func.SourceField()\n",
    "    Origin_Airport: str = func.SourceField()\n",
    "    Destination_City: str = func.SourceField()\n",
    "    Destination_Airport: str = func.SourceField()\n",
    "    Departure_Time: str = func.SourceField()\n",
    "    Arrival_Time: str = func.SourceField()\n",
    "    Duration: str = func.SourceField()\n",
    "    Price: str = func.SourceField()\n",
    "    Available_Seats: str = func.SourceField()\n",
    "    Class: str = func.SourceField()\n",
    "    Flight_details: str = func.SourceField()\n",
    "    vector: Vector(func.ndims()) = func.VectorField()\n",
    "\n",
    "\n",
    "class HotelWords(LanceModel):\n",
    "    id: str = func.SourceField()  # Index column\n",
    "    ID: str = func.SourceField()\n",
    "    Name: str = func.SourceField()\n",
    "    City: str = func.SourceField()\n",
    "    Neighborhood: str = func.SourceField()\n",
    "    Address: str = func.SourceField()\n",
    "    Stars: str = func.SourceField()\n",
    "    Price_Per_Night: str = func.SourceField()\n",
    "    Amenities: str = func.SourceField()\n",
    "    Rating: str = func.SourceField()\n",
    "\n",
    "    all_hotel_details: str = func.SourceField()\n",
    "    vector: Vector(func.ndims()) = func.VectorField()\n",
    "\n",
    "\n",
    "# Reset index to make it a column\n",
    "flight_data = flight_data.reset_index().rename(columns={\"index\": \"id\"})\n",
    "df_hotel = df_hotel.reset_index().rename(columns={\"index\": \"id\"})\n",
    "\n",
    "# Create and populate tables\n",
    "flight_table = db.create_table(\n",
    "    \"hotel_recommandation1s\", schema=FlightWords, mode=\"overwrite\"\n",
    ")\n",
    "flight_table.create_fts_index(\n",
    "    [\"Airline\", \"Flight_details\"], use_tantivy=True, replace=True\n",
    ")\n",
    "flight_table.add(data=flight_data)\n",
    "\n",
    "hotel_table = db.create_table(\n",
    "    \"hotel_recommendations\", schema=HotelWords, mode=\"overwrite\"\n",
    ")\n",
    "hotel_table.create_fts_index(\n",
    "    [\"City\", \"all_hotel_details\"], use_tantivy=True, replace=True\n",
    ")\n",
    "hotel_table.add(data=df_hotel)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "J_n07WvpeXII"
   },
   "source": [
    "#### prosessing user query to strctured data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "3125YZKPeLTv"
   },
   "outputs": [],
   "source": [
    "def format_flight_details(flight_details):\n",
    "    \"\"\"Extract and format flight details from the string\"\"\"\n",
    "    parts = {}\n",
    "    try:\n",
    "        # Split the flight details string into components\n",
    "        details = flight_details[\"Flight_details\"].split(\"|\")\n",
    "        parts[\"flight_info\"] = details[0].strip()\n",
    "        parts[\"route\"] = details[1].strip()\n",
    "        parts[\"timing\"] = details[2].strip()\n",
    "        parts[\"price_seats\"] = details[3].strip()\n",
    "        parts[\"class\"] = details[4].strip() if len(details) > 4 else \"N/A\"\n",
    "    except:\n",
    "        parts[\"raw_details\"] = flight_details[\"Flight_details\"]\n",
    "    return parts\n",
    "\n",
    "\n",
    "def format_search_results(results):\n",
    "    \"\"\"Format search results in a clean, structured way\"\"\"\n",
    "    formatted_results = {\"total_results\": len(results), \"flights\": []}\n",
    "\n",
    "    for flight in results:\n",
    "        details = format_flight_details(flight)\n",
    "        formatted_flight = {\n",
    "            \"flight_number\": flight[\"Flight_Number\"],\n",
    "            \"airline\": flight.get(\"Airline\", \"N/A\"),\n",
    "            \"origin\": flight.get(\"Origin_City\", \"N/A\"),\n",
    "            \"destination\": flight.get(\"Destination_City\", \"N/A\"),\n",
    "            \"departure\": flight.get(\"Departure_Time\", \"N/A\"),\n",
    "            \"price\": flight.get(\"Price\", \"N/A\"),\n",
    "            \"details\": details,\n",
    "        }\n",
    "        formatted_results[\"flights\"].append(formatted_flight)\n",
    "\n",
    "    return formatted_results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "vdh34RJMpTh5"
   },
   "outputs": [],
   "source": [
    "# Define tools for flight search\n",
    "def search_flights(query: str) -> Dict:\n",
    "    \"\"\"Search flights using LanceDB with improved formatting\"\"\"\n",
    "    try:\n",
    "        # Get structured parameters from GPT\n",
    "        completion = client.chat.completions.create(\n",
    "            model=\"gpt-4\",\n",
    "            messages=[\n",
    "                {\n",
    "                    \"role\": \"user\",\n",
    "                    \"content\": f\"\"\"Query String: {query}\n",
    "\n",
    "        Now from the query string above extract these following entities for flight search:\n",
    "        1. Origin City/Airport: Extract the departure city or airport\n",
    "        2. Destination City/Airport: Extract the destination city or airport\n",
    "        3. Travel Date/Time (if mentioned): Extract any date or time preferences\n",
    "        4. Class Preference (if mentioned): Extract class preference (Economy, Business, etc.)\n",
    "        5. Airline Preference (if mentioned): Extract specific airline if mentioned\n",
    "        6. Price Range (if mentioned): Extract any price preferences or budget constraints\n",
    "        7. Other Preferences: Extract any other specific requirements\n",
    "\n",
    "        Return ONLY a Python dictionary with these keys. If any value is not mentioned in the query, set it as None. Do not include any additional text or explanation.\"\"\",\n",
    "                }\n",
    "            ],\n",
    "        )\n",
    "\n",
    "        # Parse the GPT response using ast.literal_eval for safety\n",
    "        try:\n",
    "            params = ast.literal_eval(completion.choices[0].message.content)\n",
    "        except:\n",
    "            # Fallback to using the raw query if parsing fails\n",
    "            params = {\"query\": query}\n",
    "\n",
    "        # Search flights using the parameters\n",
    "        results = (\n",
    "            flight_table.search(str(params))\n",
    "            .limit(5)\n",
    "            .select(\n",
    "                [\n",
    "                    \"Flight_Number\",\n",
    "                    \"Airline\",\n",
    "                    \"Origin_City\",\n",
    "                    \"Destination_City\",\n",
    "                    \"Departure_Time\",\n",
    "                    \"Price\",\n",
    "                    \"Flight_details\",\n",
    "                ]\n",
    "            )\n",
    "            .to_list()\n",
    "        )\n",
    "\n",
    "        # Format the results using our formatting functions\n",
    "        formatted_results = format_search_results(results)\n",
    "        return formatted_results\n",
    "    except Exception as e:\n",
    "        # Return a structured error response\n",
    "        return {\"error\": str(e), \"status\": \"error\", \"total_results\": 0, \"flights\": []}\n",
    "\n",
    "\n",
    "def search_hotels(query: str) -> Dict:\n",
    "    \"\"\"Search hotels using LanceDB with improved formatting\"\"\"\n",
    "    try:\n",
    "        # Get structured parameters from GPT\n",
    "        completion = client.chat.completions.create(\n",
    "            model=\"gpt-4\",\n",
    "            messages=[\n",
    "                {\n",
    "                    \"role\": \"user\",\n",
    "                    \"content\": f\"\"\"Query String: {query}\n",
    "\n",
    "        Now from the query string above extract these following entities for hotel search:\n",
    "        1. City: Extract the city where the user wants to stay\n",
    "        2. Neighborhood (if mentioned): Extract specific area or neighborhood preference\n",
    "        3. Price Range (if mentioned): Extract budget constraints or price preferences per night\n",
    "        4. Star Rating (if mentioned): Extract preferred hotel star rating\n",
    "        5. Amenities (if mentioned): Extract specific amenities requirements\n",
    "        6. Guest Rating (if mentioned): Extract minimum rating requirements\n",
    "        7. Special Requirements (if mentioned): Extract any other specific needs\n",
    "\n",
    "        Return ONLY a Python dictionary with these keys. If any value is not mentioned in the query, set it as None. Do not include any additional text or explanation.\"\"\",\n",
    "                }\n",
    "            ],\n",
    "        )\n",
    "\n",
    "        # Parse the GPT response using ast.literal_eval for safety\n",
    "        try:\n",
    "            params = ast.literal_eval(completion.choices[0].message.content)\n",
    "        except:\n",
    "            # Fallback to using the raw query if parsing fails\n",
    "            params = {\"query\": query}\n",
    "\n",
    "        # Search hotels using the parameters\n",
    "        results = (\n",
    "            hotel_table.search(str(params))\n",
    "            .limit(5)\n",
    "            .select(\n",
    "                [\n",
    "                    \"Name\",\n",
    "                    \"City\",\n",
    "                    \"Neighborhood\",\n",
    "                    \"Stars\",\n",
    "                    \"Price_Per_Night\",\n",
    "                    \"Rating\",\n",
    "                    \"Amenities\",\n",
    "                    \"all_hotel_details\",\n",
    "                ]\n",
    "            )\n",
    "            .to_list()\n",
    "        )\n",
    "\n",
    "        # Format the results\n",
    "        formatted_results = {\n",
    "            \"total_results\": len(results),\n",
    "            \"hotels\": [\n",
    "                {\n",
    "                    \"name\": hotel[\"Name\"],\n",
    "                    \"city\": hotel[\"City\"],\n",
    "                    \"neighborhood\": hotel[\"Neighborhood\"],\n",
    "                    \"stars\": hotel[\"Stars\"],\n",
    "                    \"price_per_night\": hotel[\"Price_Per_Night\"],\n",
    "                    \"rating\": hotel[\"Rating\"],\n",
    "                    \"amenities\": hotel[\"Amenities\"],\n",
    "                    \"details\": hotel[\"all_hotel_details\"],\n",
    "                }\n",
    "                for hotel in results\n",
    "            ],\n",
    "        }\n",
    "\n",
    "        return formatted_results\n",
    "    except Exception as e:\n",
    "        # Return a structured error response\n",
    "        return {\"error\": str(e), \"status\": \"error\", \"total_results\": 0, \"hotels\": []}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "rTpyNSiepTk_",
    "outputId": "afae1cd6-595c-4686-cade-5c48335128e5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Flight Query Result: {'messages': [HumanMessage(content='I need a flight from Bangalore to Mumbai tomorrow', additional_kwargs={}, response_metadata={}, id='e26ff013-6a20-4b6e-ab42-a69a9bc06445'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_pG6xNJ4lsXNP9g3eReIxQ2I9', 'function': {'arguments': '{\\n  \"query\": \"flights from Bangalore to Mumbai on 2025-03-09\"\\n}', 'name': 'search_flights'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 30, 'prompt_tokens': 451, 'total_tokens': 481, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4-0613', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='flight_assistant', id='run-7b828d97-3571-46ad-abbb-7f98a6eb18e5-0', tool_calls=[{'name': 'search_flights', 'args': {'query': 'flights from Bangalore to Mumbai on 2025-03-09'}, 'id': 'call_pG6xNJ4lsXNP9g3eReIxQ2I9', 'type': 'tool_call'}], usage_metadata={'input_tokens': 451, 'output_tokens': 30, 'total_tokens': 481, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='{\"total_results\": 5, \"flights\": [{\"flight_number\": \"AK132\", \"airline\": \"Akasa Air\", \"origin\": \"Ahmedabad\", \"destination\": \"Mumbai\", \"departure\": \"9:30\", \"price\": \"8590.63\", \"details\": {\"flight_info\": \"AK132 - Akasa Air\", \"route\": \"Ahmedabad (AMD - Sardar Vallabhbhai Patel Intl) → Mumbai (BOM - Chhatrapati Shivaji Intl)\", \"timing\": \"Departure: 9:30, Arrival: 11:30, Duration: 2h 00m\", \"price_seats\": \"₹8590.63, 22 seats left\", \"class\": \"Class: Business\"}}, {\"flight_number\": \"SP364\", \"airline\": \"SpiceJet\", \"origin\": \"Goa\", \"destination\": \"Kolkata\", \"departure\": \"13:30\", \"price\": \"7902.01\", \"details\": {\"flight_info\": \"SP364 - SpiceJet\", \"route\": \"Goa (GOI - Goa Dabolim Airport) → Kolkata (CCU - Netaji Subhas Chandra Bose Intl)\", \"timing\": \"Departure: 13:30, Arrival: 16:30, Duration: 3h 15m\", \"price_seats\": \"₹7902.01, 89 seats left\", \"class\": \"Class: Economy\"}}, {\"flight_number\": \"SP436\", \"airline\": \"SpiceJet\", \"origin\": \"Goa\", \"destination\": \"Kolkata\", \"departure\": \"7:00\", \"price\": \"10768.75\", \"details\": {\"flight_info\": \"SP436 - SpiceJet\", \"route\": \"Goa (GOI - Goa Dabolim Airport) → Kolkata (CCU - Netaji Subhas Chandra Bose Intl)\", \"timing\": \"Departure: 7:00, Arrival: 9:00, Duration: 2h 00m\", \"price_seats\": \"₹10768.75, 84 seats left\", \"class\": \"Class: Economy\"}}, {\"flight_number\": \"GO365\", \"airline\": \"Go First\", \"origin\": \"Kolkata\", \"destination\": \"Chennai\", \"departure\": \"22:45\", \"price\": \"11365.27\", \"details\": {\"flight_info\": \"GO365 - Go First\", \"route\": \"Kolkata (CCU - Netaji Subhas Chandra Bose Intl) → Chennai (MAA - Chennai Intl)\", \"timing\": \"Departure: 22:45, Arrival: 23:45, Duration: 1h 45m\", \"price_seats\": \"₹11365.27, 88 seats left\", \"class\": \"Class: Economy\"}}, {\"flight_number\": \"AI507\", \"airline\": \"AirAsia India\", \"origin\": \"Kolkata\", \"destination\": \"Bangalore\", \"departure\": \"23:00\", \"price\": \"9273.71\", \"details\": {\"flight_info\": \"AI507 - AirAsia India\", \"route\": \"Kolkata (CCU - Netaji Subhas Chandra Bose Intl) → Bangalore (BLR - Kempegowda Intl)\", \"timing\": \"Departure: 23:00, Arrival: 0:00, Duration: 1h 45m\", \"price_seats\": \"₹9273.71, 28 seats left\", \"class\": \"Class: Business\"}}]}', name='search_flights', id='942a1c96-3c4d-4046-b115-7eaa5c1a2d06', tool_call_id='call_pG6xNJ4lsXNP9g3eReIxQ2I9'), AIMessage(content=\"I'm sorry, but it seems there has been a mistake in the search. I was unable to find direct flights from Bangalore to Mumbai for tomorrow. Could you please confirm the locations or do you want to retry with different search criteria?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 1243, 'total_tokens': 1292, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4-0613', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='flight_assistant', id='run-2dd5f8e6-2259-462b-98e5-8e058c860368-0', usage_metadata={'input_tokens': 1243, 'output_tokens': 49, 'total_tokens': 1292, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}\n"
     ]
    }
   ],
   "source": [
    "# Create handoff tools\n",
    "transfer_to_hotel_assistant = create_handoff_tool(\n",
    "    agent_name=\"hotel_assistant\",\n",
    "    description=\"Transfer user to the hotel-booking assistant that can search for and book hotels.\",\n",
    ")\n",
    "\n",
    "transfer_to_flight_assistant = create_handoff_tool(\n",
    "    agent_name=\"flight_assistant\",\n",
    "    description=\"Transfer user to the flight-booking assistant that can search for and book flights.\",\n",
    ")\n",
    "\n",
    "\n",
    "# Define agent prompt\n",
    "def make_prompt(base_system_prompt: str) -> Callable:\n",
    "    def prompt(state: dict, config: RunnableConfig) -> list:\n",
    "        system_prompt = base_system_prompt + f\"\\nToday is: {datetime.datetime.now()}\"\n",
    "        return [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
    "\n",
    "    return prompt\n",
    "\n",
    "\n",
    "# Initialize the LLM\n",
    "model = ChatOpenAI(model=\"gpt-4\")\n",
    "\n",
    "\n",
    "# Define flight assistant\n",
    "flight_assistant = create_react_agent(\n",
    "    model,\n",
    "    [search_flights, transfer_to_hotel_assistant],\n",
    "    prompt=make_prompt(\n",
    "        \"\"\"You are a flight booking assistant for domestic Indian flights only. You help users find flights within India using the search_flights tool.\n",
    "    When users ask about hotels, transfer them to the hotel assistant.\n",
    "\n",
    "    Important constraints and behaviors:\n",
    "    1. You ONLY handle domestic flights within India\n",
    "    2. You have deep knowledge of Indian geography and can identify Indian cities vs international locations\n",
    "    3. For international flight requests:\n",
    "       - Politely explain you only handle domestic Indian flights\n",
    "       - Suggest they try a different service for international travel\n",
    "    4. For non-travel queries:\n",
    "       - Politely redirect them to ask about Indian domestic flights or hotels\n",
    "\n",
    "    The search_flights tool returns structured data with flight details. For each flight, you'll receive:\n",
    "    - Basic info (flight number, airline, origin, destination, departure time, price)\n",
    "    - Detailed breakdown of the flight including route, timing, price/seats, and class information\n",
    "\n",
    "    When handling flight queries:\n",
    "    1. Use your knowledge to verify both cities are valid Indian destinations\n",
    "    2. If you're unsure about a location being in India, ask the user for clarification\n",
    "    3. Present flight information in a clear, organized manner:\n",
    "       - Highlight key details like timing, price, and special features\n",
    "       - Point out relevant matches to user preferences\n",
    "    4. Always ask if they'd like to:\n",
    "       - Proceed with booking\n",
    "       - See more options\n",
    "       - Try different search criteria\n",
    "\n",
    "    Remember to:\n",
    "    - Transfer hotel-related queries to the hotel assistant\n",
    "    - Use your knowledge to validate Indian locations\n",
    "    - Maintain a helpful and informative tone\"\"\"\n",
    "    ),\n",
    "    name=\"flight_assistant\",\n",
    ")\n",
    "\n",
    "# Define hotel assistant\n",
    "hotel_assistant = create_react_agent(\n",
    "    model,\n",
    "    [search_hotels, transfer_to_flight_assistant],\n",
    "    prompt=make_prompt(\n",
    "        \"\"\"You are a hotel booking assistant for hotels in India only. You help users find hotels using the search_hotels tool.\n",
    "    When users ask about flights, transfer them to the flight assistant.\n",
    "\n",
    "    Important constraints and behaviors:\n",
    "    1. You ONLY handle hotels within India\n",
    "    2. You have deep knowledge of Indian geography and can identify Indian cities vs international locations\n",
    "    3. For international hotel requests:\n",
    "       - Politely explain you only handle Indian hotels\n",
    "       - Suggest they try a different service for international accommodations\n",
    "    4. For non-travel queries:\n",
    "       - Politely redirect them to ask about Indian hotels or flights\n",
    "\n",
    "    When handling hotel queries:\n",
    "    1. Use your knowledge to verify the requested city is in India\n",
    "    2. If you're unsure about a location being in India, ask the user for clarification\n",
    "    3. For valid Indian cities:\n",
    "       - Search for exact matches in the requested city\n",
    "       - If no hotels found, suggest nearby popular Indian cities\n",
    "    4. Present hotel information clearly, highlighting:\n",
    "       - Hotel name and rating\n",
    "       - Location and neighborhood\n",
    "       - Price per night\n",
    "       - Key amenities\n",
    "       - Guest ratings\n",
    "    5. Filter and validate results:\n",
    "       - Only show hotels in the requested city\n",
    "       - Ensure all locations are within India\n",
    "    6. If limited options are found:\n",
    "       - Clearly state the limitation\n",
    "       - Suggest alternative nearby Indian cities\n",
    "\n",
    "    Always ask if they'd like to:\n",
    "    - See specific hotel details\n",
    "    - Search with different criteria\n",
    "    - Try nearby cities\n",
    "    - Proceed with booking\n",
    "\n",
    "    Remember to:\n",
    "    - Transfer flight-related queries to the flight assistant\n",
    "    - Use your knowledge to validate Indian locations\n",
    "    - Maintain a helpful and informative tone\"\"\"\n",
    "    ),\n",
    "    name=\"hotel_assistant\",\n",
    ")\n",
    "\n",
    "# Create and compile the swarm\n",
    "checkpointer = InMemorySaver()\n",
    "builder = create_swarm(\n",
    "    [flight_assistant, hotel_assistant], default_active_agent=\"flight_assistant\"\n",
    ")\n",
    "\n",
    "app_planer = builder.compile(checkpointer=checkpointer)\n",
    "\n",
    "# Example usage\n",
    "if __name__ == \"__main__\":\n",
    "    config = {\"configurable\": {\"thread_id\": \"11\", \"user_id\": \"1\"}}\n",
    "\n",
    "    # Test flight query\n",
    "    result = app_planer.invoke(\n",
    "        {\n",
    "            \"messages\": [\n",
    "                {\n",
    "                    \"role\": \"user\",\n",
    "                    \"content\": \"I need a flight from Bangalore to Mumbai tomorrow\",\n",
    "                }\n",
    "            ]\n",
    "        },\n",
    "        config,\n",
    "    )\n",
    "    print(\"Flight Query Result:\", result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "D23YD8rbWDot"
   },
   "source": [
    "###### We are using the same `thread_id` to maintain continuity in memory for streaming purposes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "0SYndnjLpc16",
    "outputId": "ad6679fd-77aa-4d28-93d8-d571e90b5a4f"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "can you check delhi hotels\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: transfer_to_hotel_assistant\n",
      "\n",
      "Successfully transferred to hotel_assistant\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Name: hotel_assistant\n",
      "\n",
      "Here are some hotels in Delhi and nearby areas:\n",
      "\n",
      "**Delhi**\n",
      "\n",
      "1. [Hotel Delhi Residency, Saket](link)\n",
      "   - Price per night: ₹16368.2\n",
      "   - Guest rating: 4.2/5\n",
      "   - Amenities: Free Wi-Fi, Fitness Center, Airport Shuttle, Room Service\n",
      "   - Neighborhood: Saket\n",
      "\n",
      "**Nearby cities**\n",
      "\n",
      "1. [Luxury Ahmedabad Inn, Ahmedabad](link)\n",
      "   - Price per night: ₹2450.96\n",
      "   - Guest rating: 4.5/5\n",
      "   - Amenities: Free Wi-Fi, Restaurant, Spa, Parking, Swimming Pool, Laundry Service\n",
      "   - Neighborhood: CG Road\n",
      "\n",
      "2. [The Ahmedabad Residency, Ahmedabad](link)\n",
      "   - Price per night: ₹9278.69\n",
      "   - Guest rating: 3.2/5\n",
      "   - Amenities: Room Service, Conference Room, Swimming Pool, Bar, Restaurant\n",
      "   - Neighborhood: Satellite\n",
      "\n",
      "3. [Luxury Ahmedabad Palace, Ahmedabad](link)\n",
      "   - Price per night: ₹11028.44\n",
      "   - Guest rating: 5/5\n",
      "   - Amenities: Airport Shuttle, Room Service, Business Center, Breakfast Included, Swimming Pool, Conference Room, Laundry Service\n",
      "   - Neighborhood: Satellite\n",
      "\n",
      "4. [Comfort Chennai Plaza, Chennai](link)\n",
      "   - Price per night: ₹9773.18\n",
      "   - Guest rating: 5/5\n",
      "   - Amenities: Swimming Pool, Spa, Parking, Laundry Service, Restaurant, Room Service\n",
      "   - Neighborhood: Mylapore\n",
      "\n",
      "Please let me know if you would like more details on any of these options or if you have different criteria for your search.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\"role\": \"user\", \"content\": \"can you check delhi hotels\"}\n",
    "for chunk in app_planer.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "CT6VsvKEryi7",
    "outputId": "854db708-f0b0-47f4-c86f-836f87f9d4f6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "give me pune hotels\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Name: hotel_assistant\n",
      "\n",
      "Here are some hotels in Pune:\n",
      "\n",
      "1. [Hotel Pune Resort, Baner](link)\n",
      "   - Price per night: ₹10486.14\n",
      "   - Guest rating: 3.9/5\n",
      "   - Amenities: Restaurant, Conference Room, Laundry Service, Swimming Pool, Bar, Parking, Business Center\n",
      "   - Neighborhood: Baner\n",
      "\n",
      "2. [Luxury Pune Plaza, Baner](link)\n",
      "   - Price per night: ₹3236.25\n",
      "   - Guest rating: 4.7/5\n",
      "   - Amenities: Breakfast Included, Business Center, Laundry Service, Spa\n",
      "   - Neighborhood: Baner\n",
      "\n",
      "3. [The Pune Hotel, Hinjewadi](link)\n",
      "   - Price per night: ₹11987.88\n",
      "   - Guest rating: 4.8/5\n",
      "   - Amenities: Concierge, Fitness Center, Parking, Bar, Business Center, Conference Room\n",
      "   - Neighborhood: Hinjewadi\n",
      "\n",
      "4. [Luxury Pune Resort, Viman Nagar](link)\n",
      "   - Price per night: ₹6429.43\n",
      "   - Guest rating: 3.8/5\n",
      "   - Amenities: Conference Room, Laundry Service, Concierge\n",
      "   - Neighborhood: Viman Nagar\n",
      "\n",
      "5. [Grand Pune Resort, Baner](link)\n",
      "   - Price per night: ₹11043.21\n",
      "   - Guest rating: 3.7/5\n",
      "   - Amenities: Free Wi-Fi, Conference Room, Breakfast Included, Swimming Pool\n",
      "   - Neighborhood: Baner\n",
      "\n",
      "Please let me know if you would like more details on any of these options or if you have different criteria for your search.\n"
     ]
    }
   ],
   "source": [
    "input_message = {\"role\": \"user\", \"content\": \"give me pune hotels\"}\n",
    "for chunk in app_planer.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "VqKD-o8JrMb0",
    "outputId": "3eb17924-7d0c-4c11-8df3-e3df24c2685a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "now check flights from pune to delhi\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: transfer_to_flight_assistant\n",
      "\n",
      "Successfully transferred to flight_assistant\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Name: flight_assistant\n",
      "\n",
      "I found some flights from Pune to Delhi for you:\n",
      "\n",
      "1. Flight SP992 by SpiceJet\n",
      "   - Departure: 21:45\n",
      "   - Duration: 2h 00m\n",
      "   - Price: ₹6563.82\n",
      "   - Seats Left: 66\n",
      "   - Class: Economy\n",
      "\n",
      "Would you like to proceed with this flight or see more options?\n"
     ]
    }
   ],
   "source": [
    "input_message = {\"role\": \"user\", \"content\": \"now check flights from pune to delhi\"}\n",
    "for chunk in app_planer.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
